package gl.drawer

import gl.util.Resource
import gl.window
import org.joml.Matrix4f
import org.lwjgl.opengl.GL44C.*

/**
 * 一般渲染方法
 */
class Commands: Drawer() {

    // 顶点数组
    private val positions = floatArrayOf(
        -1f, -1f, 0f, 1f,
         1f, -1f, 0f, 1f,
        -1f,  1f, 0f, 1f,
        -1f, -1f, 0f, 1f,
    )

    // 颜色数组
    private val colors = floatArrayOf(
        1f, 1f, 1f, 1f,
        1f, 1f, 0f, 1f,
        1f, 0f, 1f, 1f,
        0f, 1f, 1f, 1f,
    )

    // 顶点信息
    private val indices = intArrayOf(
        0, // -1f, -1f, 0f, 1f
        1, //  1f, -1f, 0f, 1f
        2, // -1f,  1f, 0f, 1f
    )

    private val matModel0 = Matrix4f().identity().translate(-3f, 0f, -5f)[FloatArray(16)]
    private val matModel1 = Matrix4f().identity().translate(-1f, 0f, -5f)[FloatArray(16)]
    private val matModel2 = Matrix4f().identity().translate( 1f, 0f, -5f)[FloatArray(16)]
    private val matModel3 = Matrix4f().identity().translate( 3f, 0f, -5f)[FloatArray(16)]

    private var vertex = 0
    private var program = 0
    private var bufferElement = 0
    private var bufferVertex = 0

    private var cullFaceEnabled = false
    private var depthEnabled = false

    private var uniModelMatrix = 0
    private var uniProjectionMatrix = 0

    override fun initialize() {
        // 着色器
        val vertexShader = glCreateShader(GL_VERTEX_SHADER)
        glShaderSource(vertexShader, Resource.getString("primitive_restart.vs.glsl"))
        glCompileShader(vertexShader)
        if (glGetShaderi(vertexShader, GL_COMPILE_STATUS) != GL_TRUE) {
            error(glGetShaderInfoLog(vertexShader))
        }
        val fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
        glShaderSource(fragmentShader, Resource.getString("primitive_restart.fs.glsl"))
        glCompileShader(fragmentShader)
        if (glGetShaderi(fragmentShader, GL_COMPILE_STATUS) != GL_TRUE) {
            error(glGetShaderInfoLog(vertexShader))
        }
        program = glCreateProgram()
        glAttachShader(program, vertexShader)
        glAttachShader(program, fragmentShader)
        glLinkProgram(program)
        glDetachShader(program, vertexShader)
        glDetachShader(program, fragmentShader)
        glDeleteShader(vertexShader)
        glDeleteShader(fragmentShader)
        if (glGetProgrami(program, GL_LINK_STATUS) != GL_TRUE) {
            error(glGetProgramInfoLog(program))
        }
        glUseProgram(program)
        // 缓冲区
        bufferElement = glGenBuffers()
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferElement)
        glBufferStorage(GL_ELEMENT_ARRAY_BUFFER, indices.size * 4L, 0)
        glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indices)
        bufferVertex = glGenBuffers()
        glBindBuffer(GL_ARRAY_BUFFER, bufferVertex)
        glBufferStorage(GL_ARRAY_BUFFER, (positions.size + colors.size) * 4L, 0)
        glBufferSubData(GL_ARRAY_BUFFER, 0, positions)
        glBufferSubData(GL_ARRAY_BUFFER, positions.size * 4L, colors)
        // 顶点
        vertex = glGenVertexArrays()
        glBindVertexArray(vertex)
        glEnableVertexAttribArray(0) // position
        glVertexAttribPointer(0, 4, GL_FLOAT, false, 0, 0L)
        glEnableVertexAttribArray(1) // color
        glVertexAttribPointer(1, 4, GL_FLOAT, false, 0, positions.size * 4L)
        // 其他
        cullFaceEnabled = glIsEnabled(GL_CULL_FACE)
        depthEnabled = glIsEnabled(GL_DEPTH_TEST)
    }

    override fun resume() {
        super.resume()
        onResize(window.width, window.height)
        glClearColor(0f, 0f, 0f, 0f)
        glUseProgram(program)
        uniModelMatrix = glGetUniformLocation(program, "model_matrix")
        uniProjectionMatrix = glGetUniformLocation(program, "projection_matrix")
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferElement)
        glBindBuffer(GL_ARRAY_BUFFER, bufferVertex)
        glBindVertexArray(vertex)
        glEnableVertexAttribArray(0)
        glEnableVertexAttribArray(1)
        glEnable(GL_CULL_FACE)
        glDisable(GL_DEPTH_TEST)
    }

    override fun update(): Boolean {
        super.update()

        // draw arrays
        glUniformMatrix4fv(uniModelMatrix, false, matModel0)
        glDrawArrays(GL_TRIANGLES, 0, 3)

        // draw elements
        glUniformMatrix4fv(uniModelMatrix, false, matModel1)
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0)

        // draw elements base vertex
        glUniformMatrix4fv(uniModelMatrix, false, matModel2)
        glDrawElementsBaseVertex(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0, 1)

        // draw arrays instanced
        glUniformMatrix4fv(uniModelMatrix, false, matModel3)
        glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 1)

        return false
    }

    override fun stop() {
        super.stop()
        glDisableVertexAttribArray(0)
        glDisableVertexAttribArray(1)
        glBindVertexArray(0)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
        glBindBuffer(GL_ARRAY_BUFFER, 0)
        glUseProgram(0)
        if (!cullFaceEnabled) {
            glDisable(GL_CULL_FACE)
        }
        if (depthEnabled) {
            glEnable(GL_DEPTH_TEST)
        }
    }

    override fun terminal() {
        glDeleteVertexArrays(vertex)
        glDeleteBuffers(intArrayOf(bufferElement, bufferVertex))
        glDeleteProgram(program)
    }

    override fun onResize(width: Int, height: Int) {
        val aspect = height / width.toFloat()
        if (isInitialized) {
            val frustum = Matrix4f().frustum(-1f, 1f, -aspect, aspect, 1f, 500f)
            glUniformMatrix4fv(uniProjectionMatrix, false, frustum[FloatArray(16)])
        }
    }
}